C++入门系列博客三 引用和指针

C++ 引用和指针


作者:AceTan,转载请标明出处!


引用和指针对于C++来说很重要,是学习C++绕不过去的一道坎。

引用

引用(reference) 就是给对象起别名。对引用的操作与对变量直接操作完全一样。

这里说的引用泛指“左值引用(lvalue reference)”,C++11新增了一种引用,即所谓的“右值引用(rvalue reference)”,这里不作讨论。

引用即别名,引用并非一个对象,不能定义引用的引用。

定义引用时,程序把引用和它的初始值进行绑定(bind)在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用和它的初始值对象一直是绑定在一起的,死也不分开。

举个栗子:胡一菲的小名叫小菲菲,那么对小菲菲的所有操作既是对胡一菲的操作。同样,小菲菲这个名字就是胡一菲绑定在一起了。当然,胡一菲还可以有其他的别名,比如叫菲菲菲。但需要注意的是,一旦有了这个小名,那么这个小名就不能指代其他人。不然会死的很惨。

引用的定义

引用定义时就需要初始化。允许一条语句定义多个引用,其中每个引用标示符都必须以符号&开头。

int value = 1;
int &refValue = value;      // refValue 指向value
int &refValue2;             // 报错,引用必须初始化 

引用的写法也还可以是这样:

int value = 1;
int& refValue = value;      // refValue 指向value
int& refValue2;             // 报错,引用必须初始化 

注意&符号和int之间是否紧挨着,两种写法都是对的(下面介绍的指针也可以用这两种写法), 这个只是个人的代码风格问题,并无实质差异。

引用更为常见的用法应该是作为函数的参数,利用引用的特性,可以让一个函数返回多个"返回值"。另外,当使用一个类作为函数参数时,比较好的做法也是使用引用。这样可以减少创建类副本的一些开销。请看一个具体的示例:

#include <iostream>

using namespace std;

const double PI = 3.14;

// 圆的数据结构
struct Circle
{
    double radius;          // 半径
    double circumference;   // 周长
    double square;          // 面积
};

// 给出圆的半径,计算圆的周长和面积,返回是否计算成功。
bool CalCirle(Circle& c)
{
    // 给的圆的半径为负数,返回计算错误
    if (c.radius <= 0)
    {
        return false;
    }

    // 周长的计算
    c.circumference = 2 * PI * c.radius;

    // 面积的计算
    c.square = PI * c.radius * c.radius;

    return true;

}

int main()
{
    Circle c;
    cout << "请输入圆的半径:";
    cin >> c.radius;

    if (CalCirle(c))
    {
        cout << "该圆的周长是" << c.circumference<< endl;
        cout << "该圆的面积是" << c.square << endl;
    }
    else
    {
        cout << "您输入的半径不合法!" << endl;
    }

    return 0;
}

指针

指针(pointer) 是“指向(point to)”另外一种类型的复合类型。

复合类型(compound type) 是指基于其他类型定义的类型。比较常见的是指针和引用。

指针本身就是一个对象,允许对指针的复制和拷贝,而且,在指针的生命周期内,它可以先后指向几个不同的对象。指针和其他变量一样,定义的时候可以不赋值。如果指针未被初始化,那么它将拥有一个不确定的值。指针未初始化和空指针经常引起程序崩溃。

指针通常难以理解,即使是有经验的程序员也常常因为调试指针引发的bug而备受折磨。

举个栗子,便于理解指针:指针好比门牌号,它所指向的是这个房间的东西,它本身也在有值的,它就是门牌上的号码。你可以把门牌号取下来,挂在别的房间,那么这个门牌号就指向了另一个房间。当然,你觉得有个门牌号不吉利,把它重新刷成其他门牌号,这样也是允许的。但你不能把两个不同的房间都挂一样的门牌号,那么别人就分不清了。某天你打开了某个房间(你需要钥匙,钥匙就是解引用),发现里面有个门牌号,这个就是多级指针。房间里的门牌号告诉你,你要找的东西在这个门牌号所指代的房间里。

指针存放某个对象的地址,要想获得该地址,需要使用取地址运算符(操作符&)

注意这里的取地址运算符&声明引用时标示符以符号&开头的意义和用法是不一样的。

int value = 2;
int* p = &value;    // p存放变量value的地址,或者说p是指向变量value的指针

利用指针访问对象:如果一个指针指向一个对象,则允许解引用符(操作符*) 来访问该对象。

int value = 2;
int* p = &value;        // p存放变量value的地址,或者说p是指向变量value的指针

*p = *p * 3;            // 取这个变量,并乘以3

cout << *p << endl;     // 输出结果为6
cout << value << endl;  // 输出结果为6

指针可以为空(引用是不可以的)。空指针不指向任何对象,一个良好的习惯就是使用指针前,一定要判断是否为空。

得到空指针最直接的方法是用字面值nullptr 来初始化指针。(C++11引入的一种方法)

以前的方法是将指针初始化为字面值0或者NULL。(NULL为一个预处理变量,它的值就是0)。

void* 指针 是一个特殊类型的指针,它可用于存放任意对象的地址。

指针的指针 指针级数是没有限制的。但常见的也就是一级指针和二级指针,二级以上比较少见。给出一个多级指针的例子:

#include <iostream>

using namespace std;

int main()
{
    int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    int b[3][4] = 
    {
        11, 22, 33,
        44, 55, 66,
        77, 88, 99,
        111, 222, 333
    };

    int *p1 = nullptr, *p2 = nullptr, **p3 = nullptr;

    p1 = a;     // p1指向a数组
    p3 = &p1;   // p3指向p1

    for (int i = 0; i < 10; ++i)
    {
        cout << *(*p3 + i) << " ";  // 输出: 0 1 2 3 4 5 6 7 8 9
    }

    cout << endl;

    for (p1 = a; p1 - a < 10; ++p1)
    {
        p3 = &p1;
        cout << ** p3 << " ";       // 输出: 0 1 2 3 4 5 6 7 8 9
    }

    cout << endl;

    for (int i = 0; i < 3; ++i)
    {
        p2 = b[i];
        p3 = &p2;
        for (int j = 0; j < 4; ++j)
        {
            cout << *(*p3 + j) << " ";  // 输出:11 22 33 44 55 66 77 88 99 111 222 333
        }
    }

    return 0;
}

函数指针 是C++中最大优点之一了。一般来说,使用函数指针比函数引用更为方便一些。

函数指针的声明使用方式:

<想要指向的函数之 返回类型>(*函数指针的 名称)<想要指向的函数之 参数类型…>

#include <iostream>

using namespace std;

void sayHello(const char* name)
{
    cout << "Hello, " << name << endl;
}

int add(int a, int b)
{
    return a + b;
}

int main()
{
    void(*pFunc1)(const char*);
    pFunc1 = &sayHello;
    pFunc1("Ace Tan");              // 输出:Hello, Ace Tan

    int(*pFunc2)(int, int);
    pFunc2 = &add;
    cout << pFunc2(1, 2) << endl;   // 输出3

    return 0;
}

函数指针也可以作为函数的参数,在这儿不做详细介绍了。


总结

引用和指针有如下区别:

  • 修改性:指针可以重新赋值以指向另一个对象。引用在初始化时就被绑定对象,以后无法改变。

  • 非空性: 指针可为空,引用不可为空

  • 传递性: 指针传递参数和值传递是一样的,被当做局部函数来处理,不影响主调函数的实参变量。引用传递对形参的任何操作都影响了主调函数中的实参变量。


结束语

引用和指针在C++中应用非常多,也非常灵活。指针也是C++最难学的部分之一,少年,赶快去征服它吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容